﻿/*
    This file is part of TexEditorControl.

    TexEditorControl is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    TexEditorControl is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with TexEditorControl.  If not, see <http://www.gnu.org/licenses/>.   
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Gui.CompletionWindow;

namespace TexEditor
{
    /// <summary>
    /// The special editor for TeX/LaTeX files.
    /// </summary>
    public sealed class TexEditor : TextEditorControl
    {
        private static readonly TexCompletionBase texBase = TexCompletionDataProvider.LoadCompletionData();

        private static readonly ReadOnlyCollection<TexCommand> texSpecialCommands =
            TexCompletionDataProvider.GetSpecialCommands(texBase);

        /// <summary>
        /// Image list with the completion window glyphs.
        /// </summary>
        internal static ImageList il_TexImages;

        private IContainer components;

        /// <summary>
        /// Initializes a new instance of the TexEditor class.
        /// </summary>
        public TexEditor()
            : base()
        {
            InitializeComponent();
            SetHighlighting("TeX");
            Document.FoldingManager.FoldingStrategy = new TexFoldingStrategy(this);
            TextChanged += new EventHandler((s1, e1) =>
            {
                var thread = new BackgroundWorker();
                thread.DoWork +=
                    new DoWorkEventHandler((s2, e2) => { Document.FoldingManager.UpdateFoldings("", null); });
                thread.RunWorkerAsync();
            });
        }

        /// <summary>
        /// Activates the completion feature.
        /// </summary>
        /// <param name="form"></param>
        public void AttachCodeCompletion(Form form)
        {
            CodeCompletionKeyHandler.Attach(form, this);
        }

        private void InitializeComponent()
        {
            components = new Container();
            var resources = new ComponentResourceManager(typeof (TexEditor));
            il_TexImages = new ImageList(components);
            SuspendLayout();
            // 
            // textAreaPanel
            // 
            textAreaPanel.Size = new Size(300, 300);
            // 
            // il_TexImages
            // 
            il_TexImages.ImageStream = ((ImageListStreamer) (resources.GetObject("il_TexImages.ImageStream")));
            il_TexImages.TransparentColor = Color.Transparent;
            il_TexImages.Images.SetKeyName(0, "Icons.16x16.NameSpace.png");
            il_TexImages.Images.SetKeyName(1, "Icons.16x16.Property.png");
            il_TexImages.Images.SetKeyName(2, "Icons.16x16.Enum.png");
            il_TexImages.Images.SetKeyName(3, "Icons.16x16.Class.png");
            il_TexImages.Images.SetKeyName(4, "Icons.16x16.Method.png");
            il_TexImages.Images.SetKeyName(5, "Icons.16x16.Field.png");
            il_TexImages.Images.SetKeyName(6, "Icons.16x16.Event.png");
            // 
            // TexEditor
            // 
            Name = "TexEditor";
            ShowLineNumbers = false;
            Size = new Size(300, 300);
            ResumeLayout(false);
        }

        #region TeX completion data classes        

        #region Nested type: ITexEntry

        /// <summary>
        /// The base interface for TeX syntax entries.
        /// </summary>
        internal interface ITexEntry
        {
            /// <summary>
            /// Gets or sets the entry name.
            /// </summary>
            string Name { get; set; }

            /// <summary>
            /// Gets or sets the entry description.
            /// </summary>
            string Description { get; set; }
        }

        #endregion

        #region Nested type: TexArgument

        /// <summary>
        /// The TeX argument struct.
        /// </summary>
        internal sealed class TexArgument : TexEntry
        {
            /// <summary>
            /// Initializes a new instance of the TexArgument struct.
            /// </summary>
            /// <param name="name">The argument name.</param>
            /// <param name="description">The argument description.</param>
            public TexArgument(string name, string description)
                : base(name)
            {
                Description = description;
            }
        }

        #endregion

        #region Nested type: TexCommand

        /// <summary>
        /// The TeX command struct.
        /// </summary>
        internal sealed class TexCommand : TexEntry, ITexEntry
        {
            private bool _Double;
            private TexGroup _Group;
            private TexCommand _Requirement;
            private bool _Special;

            public TexCommand(string name) : base(name) {}

            /// <summary>
            /// Gets or sets the additional required command.
            /// </summary>
            public TexCommand Requirement
            {
                get
                {
                    return _Requirement;
                }

                set
                {
                    _Requirement = value;
                }
            }

            /// <summary>
            /// Gets or sets the command argument group.
            /// </summary>
            public TexGroup Group
            {
                get
                {
                    return _Group;
                }

                set
                {
                    _Group = value;
                }
            }

            /// <summary>
            /// Gets or sets the value indicating whether the command is special.
            /// </summary>
            public bool Special
            {
                get
                {
                    return _Special;
                }

                set
                {
                    _Special = value;
                }
            }

            /// <summary>
            /// Gets or sets the value indicating whether the command has two parameters.
            /// </summary>
            public bool Double
            {
                get
                {
                    return _Double;
                }

                set
                {
                    _Double = value;
                }
            }
        }

        #endregion

        #region Nested type: TexCompletionBase

        private sealed class TexCompletionBase
        {
            private List<TexCommand> _Commands = new List<TexCommand>();
            private List<TexGroup> _Groups = new List<TexGroup>();
            private List<TexWord> _Words = new List<TexWord>();

            /// <summary>
            /// Gets the TeX words.
            /// </summary>
            public ReadOnlyCollection<TexWord> Words
            {
                get
                {
                    return new ReadOnlyCollection<TexWord>(_Words);
                }
            }

            /// <summary>
            /// Gets the TeX commands.
            /// </summary>
            public ReadOnlyCollection<TexCommand> Commands
            {
                get
                {
                    return new ReadOnlyCollection<TexCommand>(_Commands);
                }
            }

            /// <summary>
            /// Gets the TeX groups.
            /// </summary>
            public ReadOnlyCollection<TexGroup> Groups
            {
                get
                {
                    return new ReadOnlyCollection<TexGroup>(_Groups);
                }
            }

            private int CompareEntries(TexEntry x, TexEntry y)
            {
                return String.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
            }

            public void SortGroups()
            {
                _Groups.Sort(CompareEntries);
            }

            public void SortWords()
            {
                _Words.Sort(CompareEntries);
            }

            public void SortCommands()
            {
                _Commands.Sort(CompareEntries);
            }

            public void Add(TexGroup group)
            {
                _Groups.Add(group);
            }

            public void Add(TexWord word)
            {
                _Words.Add(word);
            }

            public void Add(TexCommand cmd)
            {
                _Commands.Add(cmd);
            }

            public TexWord WordByName(string name)
            {
                return _Words[_Words.BinarySearch(new TexWord(name), new WordComparer())];
            }

            public TexCommand CommandByName(string name)
            {
                return _Commands[_Commands.BinarySearch(new TexCommand(name), new CommandComparer())];
            }

            public TexGroup GroupByName(string name)
            {
                return _Groups[_Groups.BinarySearch(new TexGroup(name), new GroupComparer())];
            }

            #region Nested type: CommandComparer

            private class CommandComparer : NameComparer<TexCommand> {}

            #endregion

            #region Nested type: GroupComparer

            private class GroupComparer : NameComparer<TexGroup> {}

            #endregion

            #region Nested type: NameComparer

            private class NameComparer<T> : IComparer<T> where T : ITexEntry
            {
                #region IComparer<T> Members

                public int Compare(T x, T y)
                {
                    return String.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
                }

                #endregion
            }

            #endregion

            #region Nested type: WordComparer

            private class WordComparer : NameComparer<TexWord> {}

            #endregion
        }

        #endregion

        #region Nested type: TexCompletionDataProvider

        private static class TexCompletionDataProvider
        {
            public static TexCompletionBase LoadCompletionData()
            {
                var completionBase = new TexCompletionBase();
                TexWord word;
                TexCommand command;
                TexGroup group;

                using (Stream cfg = Assembly.GetExecutingAssembly().GetManifestResourceStream(
                    "TexEditor.TexCodeCompletion.xml"))
                {
                    if (cfg == null)
                    {
                        return null;
                    }
                    XDocument doc = XDocument.Load(XmlReader.Create(cfg));
                    using (Stream schm = Assembly.GetExecutingAssembly().GetManifestResourceStream(
                        "TexEditor.TexCodeCompletion.xsd"))
                    {
                        if (schm == null)
                        {
                            return null;
                        }
                        var XSchm = new XmlSchemaSet();
                        XSchm.Add(null, XmlReader.Create(schm));
                        string validErrors = "";
                        try
                        {
                            doc.Validate(XSchm, (o, e) =>
                            {
                                validErrors += String.Format(
                                    CultureInfo.InvariantCulture,
                                    "[{0}]: {1}\n", e.Severity.ToString(), e.Message);
                            });
                        }
                        catch (NotImplementedException) {}
                        if (!String.IsNullOrEmpty(validErrors))
                        {
                            throw new XmlSchemaValidationException(validErrors);
                        }
                    }
                    XElement elem = doc.Element("CompletionProviderKeywords").Element("TeX");

                    #region Adding groups

                    foreach (XElement grp in elem.Element("Arguments").Elements())
                    {
                        group = new TexGroup(grp.Attribute("name").Value);
                        var args = new List<TexArgument>();
                        foreach (XElement arg in grp.Elements())
                        {
                            args.Add(new TexArgument(arg.Value, arg.Attribute("desc").Value));
                        }
                        group.Arguments = new ReadOnlyCollection<TexArgument>(args);
                        completionBase.Add(group);
                    }
                    completionBase.SortGroups();

                    #endregion

                    #region Adding words

                    var wordsWithReq = new Dictionary<TexWord, string>();
                    foreach (XElement wrd in elem.Element("Words").Elements())
                    {
                        word = new TexWord(wrd.Value);
                        word.Description = wrd.Attribute("desc").Value;
                        word.Space = wrd.Attribute("space") == null ? false : wrd.Attribute("space").Value == "true";
                        if (wrd.Attribute("req") != null)
                        {
                            wordsWithReq.Add(word, wrd.Attribute("req").Value);
                        }
                        completionBase.Add(word);
                    }
                    completionBase.SortWords();
                    if (wordsWithReq.Count > 0)
                    {
                        foreach (TexWord wrd in wordsWithReq.Keys)
                        {
                            wrd.Requirement = completionBase.WordByName(wordsWithReq[wrd]);
                        }
                    }

                    #endregion

                    #region Adding commands

                    var commandsWithReq = new Dictionary<TexCommand, string>();
                    foreach (XElement cmd in elem.Element("Commands").Elements())
                    {
                        command = new TexCommand(cmd.Value);
                        command.Description = cmd.Attribute("desc").Value;
                        command.Group = cmd.Attribute("grp") != null
                                            ? completionBase.GroupByName(cmd.Attribute("grp").Value)
                                            : null;
                        command.Special = cmd.Attribute("spec") == null ? false : cmd.Attribute("spec").Value == "true";
                        command.Double = cmd.Attribute("double") == null
                                             ? false
                                             : cmd.Attribute("double").Value == "true";
                        if (cmd.Attribute("req") != null)
                        {
                            commandsWithReq.Add(command, cmd.Attribute("req").Value);
                        }
                        completionBase.Add(command);
                    }
                    completionBase.SortCommands();
                    if (commandsWithReq.Count > 0)
                    {
                        foreach (TexCommand cmd in commandsWithReq.Keys)
                        {
                            cmd.Requirement = completionBase.CommandByName(commandsWithReq[cmd]);
                        }
                    }

                    #endregion

                    return completionBase;
                }
            }

            public static ReadOnlyCollection<TexCommand> GetSpecialCommands(TexCompletionBase texBase)
            {
                var commands = new List<TexCommand>();
                foreach (TexCommand cmd in texBase.Commands)
                {
                    if (cmd.Special)
                    {
                        commands.Add(cmd);
                    }
                }
                return new ReadOnlyCollection<TexCommand>(commands);
            }

            public static ReadOnlyCollection<TexCommand> GetCommands(TexCompletionBase texBase, string beginStr)
            {
                var commands = new List<TexCommand>();
                foreach (TexCommand cmd in texBase.Commands)
                {
                    if (cmd.Name.StartsWith(beginStr, StringComparison.OrdinalIgnoreCase))
                    {
                        commands.Add(cmd);
                    }
                }
                return new ReadOnlyCollection<TexCommand>(commands);
            }

            public static ReadOnlyCollection<TexWord> GetWords(TexCompletionBase texBase, string beginStr)
            {
                var words = new List<TexWord>();
                foreach (TexWord cmd in texBase.Words)
                {
                    if (cmd.Name.StartsWith(beginStr, StringComparison.OrdinalIgnoreCase))
                    {
                        words.Add(cmd);
                    }
                }
                return new ReadOnlyCollection<TexWord>(words);
            }

            public static ReadOnlyCollection<TexArgument> GetArguments(TexCommand texCommand, string beginStr)
            {
                var args = new List<TexArgument>();
                foreach (TexArgument arg in texCommand.Group.Arguments)
                {
                    if (arg.Name.StartsWith(beginStr, StringComparison.OrdinalIgnoreCase))
                    {
                        args.Add(arg);
                    }
                }
                return new ReadOnlyCollection<TexArgument>(args);
            }
        }

        #endregion

        #region Nested type: TexEntry

        /// <summary>
        /// The base class for TeX syntax entries.
        /// </summary>
        internal class TexEntry
        {
            private string _Description;
            private string _Name;

            /// <summary>
            /// Initializes a new instance of the TexEntry class.
            /// </summary>
            /// <param name="name"></param>
            public TexEntry(string name)
            {
                _Name = name;
            }

            /// <summary>
            /// Gets or sets the entry name.
            /// </summary>
            public string Name
            {
                get
                {
                    return _Name;
                }

                set
                {
                    _Name = value;
                }
            }

            /// <summary>
            /// Gets or sets the entry description.
            /// </summary>
            public string Description
            {
                get
                {
                    return _Description;
                }

                set
                {
                    _Description = value;
                }
            }
        }

        #endregion

        #region Nested type: TexGroup

        /// <summary>
        /// The TeX argument group struct.
        /// </summary>
        internal sealed class TexGroup : TexEntry, ITexEntry
        {
            private List<TexArgument> _Arguments;

            public TexGroup(string name) : base(name) {}

            /// <summary>
            /// Gets or sets the group arguments.
            /// </summary>
            public ReadOnlyCollection<TexArgument> Arguments
            {
                get
                {
                    return new ReadOnlyCollection<TexArgument>(_Arguments);
                }

                set
                {
                    var arr = new TexArgument[value.Count];
                    value.CopyTo(arr, 0);
                    _Arguments = new List<TexArgument>(arr);
                }
            }
        }

        #endregion

        #region Nested type: TexWord

        /// <summary>
        /// The TeX word struct.
        /// </summary>
        private sealed class TexWord : TexEntry, ITexEntry
        {
            private TexWord _Requirement;
            private bool _Space;

            /// <summary>
            /// Initializes a new instance of the TexWord class.
            /// </summary>
            /// <param name="name"></param>
            public TexWord(string name) : base(name) {}

            /// <summary>
            /// Gets or sets the word space requirement.
            /// </summary>
            public bool Space
            {
                get
                {
                    return _Space;
                }

                set
                {
                    _Space = value;
                }
            }

            /// <summary>
            /// Gets or sets the additional required word.
            /// </summary>
            public TexWord Requirement
            {
                get
                {
                    return _Requirement;
                }

                set
                {
                    _Requirement = value;
                }
            }
        }

        #endregion

        #endregion

        #region Completion interface implementation

        #region Nested type: CodeCompletionKeyHandler

        internal sealed class CodeCompletionKeyHandler
        {
            private CodeCompletionWindow codeCompletionWindow;
            private CodeCompletionProvider completionDataProvider;
            private TextEditorControl editor;
            private Form texEditor;

            private CodeCompletionKeyHandler(Form texEditor, TextEditorControl editor)
            {
                this.texEditor = texEditor;
                this.editor = editor;
            }

            public static CodeCompletionKeyHandler Attach(Form texEditor, TextEditorControl editor)
            {
                var h = new CodeCompletionKeyHandler(texEditor, editor);

                editor.ActiveTextAreaControl.TextArea.KeyEventHandler += h.TextAreaKeyEventHandler;

                // When the editor is disposed, close the code completion window
                editor.Disposed += h.CloseCodeCompletionWindow;

                return h;
            }

            /// <summary>
            /// Return true to handle the keypress, return false to let the text area handle the keypress
            /// </summary>
            private bool TextAreaKeyEventHandler(char key)
            {
                if (completionDataProvider != null && completionDataProvider.TexCommandInput != null &&
                    key != Char.MinValue && codeCompletionWindow != null)
                {
                    if (codeCompletionWindow.ProcessKeyEvent(key))
                        return true;
                }

                if (codeCompletionWindow != null || key == Char.MinValue)
                {
                    /*// If completion window is open and wants to handle the key, don't let the text area
                    // handle it
                    if (codeCompletionWindow.ProcessKeyEvent(key))
                        return true;*/

                    codeCompletionWindow = CodeCompletionWindow.ShowCompletionWindow(
                        texEditor, // The parent window for the completion window
                        editor, // The text editor to show the window for
                        ".tex", // Filename - will be passed back to the provider
                        completionDataProvider, // Provider to get the list of possible completions
                        key // Key pressed - will be passed to the provider
                        );
                    if (codeCompletionWindow != null)
                    {
                        // ShowCompletionWindow can return null when the provider returns an empty list
                        codeCompletionWindow.Closed += new EventHandler(CloseCodeCompletionWindow);
                    }
                    return false;
                }
                if (key == '\\')
                {
                    completionDataProvider = new CodeCompletionProvider();

                    codeCompletionWindow = CodeCompletionWindow.ShowCompletionWindow(
                        texEditor, // The parent window for the completion window
                        editor, // The text editor to show the window for
                        ".tex", // Filename - will be passed back to the provider
                        completionDataProvider, // Provider to get the list of possible completions
                        key // Key pressed - will be passed to the provider
                        );
                    if (codeCompletionWindow != null)
                    {
                        // ShowCompletionWindow can return null when the provider returns an empty list
                        codeCompletionWindow.Closed += new EventHandler(CloseCodeCompletionWindow);
                    }
                }
                if (key == '[')
                {
                    TextArea tar = editor.ActiveTextAreaControl.TextArea;
                    tar.InsertString("[]");
                    tar.Caret.Position = new TextLocation(
                        tar.Caret.Position.Column - 1, tar.Caret.Position.Line);
                    return true;
                }
                if (key == '$')
                {
                    TextArea tar = editor.ActiveTextAreaControl.TextArea;
                    if (editor.Text[tar.Caret.Offset - 1] != '$' && editor.Text[tar.Caret.Offset - 2] != '$')
                    {
                        tar.InsertString("$  $");
                        tar.Caret.Position = new TextLocation(tar.Caret.Position.Column - 2, tar.Caret.Position.Line);
                    }
                    else
                    {
                        if (editor.Text[tar.Caret.Offset - 1] == '$')
                        {
                            editor.Document.Remove(tar.Caret.Offset - 1, 1);
                            tar.Caret.Position = new TextLocation(tar.Caret.Position.Column - 1, tar.Caret.Position.Line);
                        }
                        if (editor.Text[tar.Caret.Offset - 2] == '$')
                        {
                            editor.Document.Remove(tar.Caret.Offset - 2, 4);
                            tar.Caret.Position = new TextLocation(tar.Caret.Position.Column - 2, tar.Caret.Position.Line);
                        }
                        TextLocation loc = tar.Caret.Position;
                        tar.InsertString("$$\n\n$$");
                        tar.Caret.Position = new TextLocation(0, loc.Line + 1);
                    }
                    return true;
                }
                if (key == '(')
                {
                    TextArea tar = editor.ActiveTextAreaControl.TextArea;
                    tar.InsertChar(')');
                    tar.Caret.Position = new TextLocation(tar.Caret.Position.Column - 1, tar.Caret.Position.Line);
                }
                return false;
            }

            private void CloseCodeCompletionWindow(object sender, EventArgs e)
            {
                if (codeCompletionWindow != null)
                {
                    codeCompletionWindow.Closed -= new EventHandler(CloseCodeCompletionWindow);
                    codeCompletionWindow.Dispose();
                    codeCompletionWindow = null;
                    if (completionDataProvider.TexCommandInput != null &&
                        completionDataProvider.TexCommandInput.Group != null)
                    {
                        TextAreaKeyEventHandler(Char.MinValue);
                    }
                }
            }
        }

        #endregion

        #region Nested type: CodeCompletionProvider

        internal sealed class CodeCompletionProvider : ICompletionDataProvider
        {
            public TexCommand TexCommandInput;
            private string typedString = "";

            #region ICompletionDataProvider Members

            public ImageList ImageList
            {
                get
                {
                    return il_TexImages;
                }
            }

            public string PreSelection
            {
                get
                {
                    return null;
                }
            }

            public int DefaultIndex
            {
                get
                {
                    return -1;
                }
            }

            public CompletionDataProviderKeyResult ProcessKey(char key)
            {
                if (char.IsLetterOrDigit(key) || key == '_')
                {
                    return CompletionDataProviderKeyResult.NormalKey;
                }
                else
                {
                    // key triggers insertion of selected items
                    return CompletionDataProviderKeyResult.InsertionKey;
                }
            }

            /// <summary>
            /// Called when entry should be inserted. Forward to the insertion action of the completion data.
            /// </summary>
            public bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key)
            {
                textArea.Caret.Position = textArea.Document.OffsetToPosition(insertionOffset);
                TexEntry entry = (data as TexCompletionData).Entry;
                TexCommandInput = entry is TexCommand ? (entry as TexCommand) : null;
                if (TexCommandInput != null)
                {
                    typedString = "";
                }
                return data.InsertAction(textArea, key);
            }

            public ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped)
            {
                var resultList = new List<ICompletionData>();
                if (charTyped == '\\')
                {
                    foreach (TexCommand cmd in texSpecialCommands)
                    {
                        resultList.Add(new TexCompletionData(cmd, 0, typedString.Length));
                    }
                }
                else
                {
                    if (charTyped != Char.MinValue)
                    {
                        typedString += charTyped;
                    }
                    if (TexCommandInput != null)
                    {
                        foreach (TexArgument arg in TexCompletionDataProvider.GetArguments(TexCommandInput, typedString)
                            )
                        {
                            resultList.Add(new TexCompletionData(arg, 2, typedString.Length, TexCommandInput));
                        }
                    }
                    else
                    {
                        foreach (TexCommand cmd in TexCompletionDataProvider.GetCommands(texBase, typedString))
                        {
                            resultList.Add(new TexCompletionData(cmd, 0, typedString.Length));
                        }
                        foreach (TexWord wrd in TexCompletionDataProvider.GetWords(texBase, typedString))
                        {
                            resultList.Add(new TexCompletionData(wrd, 1, typedString.Length));
                        }
                    }
                }
                return resultList.ToArray();
            }

            #endregion
        }

        #endregion

        #region Nested type: TexCompletionData

        private class TexCompletionData : TexEntry, ICompletionData
        {
            private TexCommand _ArgumentCommand;
            private int _ImageIndex;
            private int _TypedLength;
            public TexEntry Entry;

            public TexCompletionData(TexEntry entry, int imageIndex, int typedLength)
                : base(entry.Name)
            {
                Description = entry.Description;
                _ImageIndex = imageIndex;
                _TypedLength = typedLength;
                Entry = entry;
            }

            public TexCompletionData(TexEntry entry, int imageIndex, int typedLength, TexCommand argumentCommand)
                : base(entry.Name)
            {
                Description = entry.Description;
                _ImageIndex = imageIndex;
                _TypedLength = typedLength;
                Entry = entry;
                _ArgumentCommand = argumentCommand;
            }

            #region ICompletionData Members

            public int ImageIndex
            {
                get
                {
                    return _ImageIndex;
                }
            }

            public bool InsertAction(TextArea textArea, char ch)
            {
                if (ch == '\n')
                {
                    if (Entry is TexCommand)
                    {
                        var cmd = Entry as TexCommand;
                        textArea.InsertString(Name.Substring(_TypedLength) + (cmd.Double ? "{}{}" : "{}"));
                        TextLocation location = textArea.Caret.Position;
                        if (cmd.Requirement != null)
                        {
                            textArea.InsertString("\n\n\\" + cmd.Requirement.Name + "{}");
                        }
                        textArea.Caret.Position = new TextLocation(location.Column - 1, location.Line);
                    }
                    else
                    {
                        if (Entry is TexArgument)
                        {
                            textArea.Caret.Position = new TextLocation(textArea.Caret.Position.Column - 1,
                                                                       textArea.Caret.Position.Line);
                        }
                        textArea.InsertString(Name.Substring(_TypedLength));
                        TextLocation location = textArea.Caret.Position;
                        if (_ArgumentCommand != null && _ArgumentCommand.Requirement != null)
                        {
                            textArea.Caret.Position = new TextLocation(
                                2 + _ArgumentCommand.Requirement.Name.Length, location.Line + 2);
                            textArea.InsertString(Name);
                            textArea.Caret.Position = new TextLocation(0, location.Line + 1);
                        }
                        else
                        {
                            textArea.Caret.Position = new TextLocation(location.Column + 1, location.Line);
                            if (Entry is TexWord)
                            {
                                var word = Entry as TexWord;
                                if (word.Space == true)
                                {
                                    textArea.InsertString(" x");
                                    textArea.SelectionManager.ExtendSelection(new TextLocation(
                                                                                  textArea.Caret.Position.Column - 1,
                                                                                  textArea.Caret.Position.Line),
                                                                              textArea.Caret.Position);
                                }
                                if (word.Requirement != null)
                                {
                                    textArea.InsertString("\\" + word.Requirement.Name);
                                    textArea.Caret.Position = new TextLocation(location.Column, location.Line);
                                }
                            }
                        }
                    }
                }
                return true;
            }

            public double Priority
            {
                get
                {
                    return 0.0;
                }
            }

            public string Text
            {
                get
                {
                    return Name;
                }
                set
                {
                    Name = value;
                }
            }

            #endregion
        }

        #endregion

        #endregion

        #region Folding strategy

        /// <summary>
        /// Implements the TeX folding strategy.
        /// </summary>
        internal class TexFoldingStrategy : IFoldingStrategy
        {
            private TexEditor _Editor;

            public TexFoldingStrategy(TexEditor editor)
            {
                _Editor = editor;
            }

            #region IFoldingStrategy Members

            public List<FoldMarker> GenerateFoldMarkers(
                IDocument document, string fileName, object parseInformation)
            {
                try
                {
                    GetDocumentTextDelegate getTextDelegate = (doc) => { return doc.TextContent; };
                    var markers = new List<FoldMarker>();
                    var contents = (string) _Editor.Invoke(getTextDelegate, document);
                    DetectFoldings(
                        contents, "%#region ", "%#endregion",
                        "\r\n", markers, document, FoldType.Region);
                    DetectFoldings(
                        contents, "\\begin{", "\\end{", "}",
                        markers, document, FoldType.MemberBody);
                    return markers;
                }
                catch (Exception)
                {
                    return null;
                }
            }

            #endregion

            private void DetectFoldings(
                string contents, string startMarker, string endMarker, string startClosingString,
                List<FoldMarker> markers, IDocument document, FoldType foldType)
            {
                GetOffsetToPosition getLocationDelegate = (doc, offs) => { return doc.OffsetToPosition(offs); };
                int position = 0, startIndex = 0, endIndex = 0;
                var foldLocations = new Stack<FoldInfo>();
                while (true)
                {
                    startIndex = contents.IndexOf(startMarker, position);
                    endIndex = contents.IndexOf(endMarker, position);
                    if (startIndex > -1)
                    {
                        if (endIndex > -1)
                        {
                            if (startIndex < endIndex)
                            {
                                var locStart = (TextLocation) _Editor.Invoke(getLocationDelegate, document, startIndex);
                                int offset = contents.IndexOf(startClosingString, startIndex);
                                if (offset < 0)
                                {
                                    position = offset + 1;
                                    continue;
                                }
                                string foldText = contents.Substring(
                                    startIndex + startMarker.Length, offset - startMarker.Length - startIndex);
                                if (String.IsNullOrEmpty(foldText))
                                {
                                    position = offset + 1;
                                    continue;
                                }
                                foldLocations.Push(new FoldInfo(locStart, foldText));
                                position = startIndex + 1;
                                continue;
                            }
                            else
                            {
                                FoldInfo info;
                                try
                                {
                                    info = foldLocations.Pop();
                                }
                                catch (InvalidOperationException)
                                {
                                    position = endIndex + 1;
                                    continue;
                                }
                                int endIndexClosing = contents.IndexOf(startClosingString, endIndex);
                                if (endIndex == -1)
                                {
                                    position = endIndex + 1;
                                    continue;
                                }
                                endIndex = endIndexClosing;
                                var locEnd = (TextLocation) _Editor.Invoke(getLocationDelegate, document, endIndex);
                                markers.Add(new FoldMarker(
                                                document, info.Location.Line, info.Location.Column,
                                                locEnd.Line, locEnd.Column, foldType,
                                                "<" + info.Text + ">"));
                                position = endIndex + 1;
                                continue;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        if (endIndex > -1)
                        {
                            FoldInfo info;
                            try
                            {
                                info = foldLocations.Pop();
                            }
                            catch (InvalidOperationException)
                            {
                                break;
                            }
                            endIndex = contents.IndexOf(startClosingString, endIndex);
                            if (endIndex == -1)
                            {
                                break;
                            }
                            var locEnd = (TextLocation) _Editor.Invoke(getLocationDelegate, document, endIndex);
                            markers.Add(new FoldMarker(
                                            document, info.Location.Line, info.Location.Column,
                                            locEnd.Line, locEnd.Column + endMarker.Length - 1, foldType,
                                            "<" + info.Text + ">"));
                            position = endIndex + 1;
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            #region Nested type: FoldInfo

            /// <summary>
            /// Fold information structure.
            /// </summary>
            private struct FoldInfo
            {
                public TextLocation Location;
                public string Text;

                public FoldInfo(TextLocation location, string text)
                {
                    Location = location;
                    Text = text;
                }
            }

            #endregion

            #region Nested type: GetDocumentTextDelegate

            private delegate string GetDocumentTextDelegate(IDocument document);

            #endregion

            #region Nested type: GetOffsetToPosition

            private delegate TextLocation GetOffsetToPosition(IDocument document, int offset);

            #endregion
        }

        #endregion
    }
}